﻿/* ============================================================================
(c) Copyright 2012 Hewlett-Packard Development Company, L.P.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights to
use, copy, modify, merge,publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
============================================================================ */
using System;
using HPCloud.Objects.Domain;
using HPCloud.Objects.Utility;
using System.Collections.Specialized;
using System.Net.Http;
using System.Diagnostics.Contracts;
using HPCloud.Common;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Collections;
using System.Management.Automation;

namespace HPCloud.Objects.DataAccess.Storage
{
    public class CreateContainerOperationInfoEventArgs
    {
        private string _containerName;

        public CreateContainerOperationInfoEventArgs(string containerName)
        {
            _containerName = containerName;
         }

        public string ContainerName
        {
            get { return _containerName; }
            set { _containerName = value; }
        }
    }

    public class CopyOperationInfoEventArgs
    {
        private long _BytesCopied = 0;
        private string _filename;
        private string _exceptionMessage;

        public string ExceptionMessage
        {
            get { return _exceptionMessage; }
            set { _exceptionMessage = value; }
        }

        public CopyOperationInfoEventArgs(string filename, long bytesCopied)
        {
            _filename = filename;
            _BytesCopied = bytesCopied;
        }

        public string Filename
        {
            get { return _filename; }
            set { _filename = value; }
        }
        public long BytesCopied
        {
            get { return _BytesCopied; }
            set { _BytesCopied = value; }
        }
    }

    public class StorageObjectRepository : BaseRepository, IStorageObjectRepository
    {
        const string cFolderMarker = "folder.txt";
        const string cCopySource = "X-Copy-From";
        const string cDelimiter = "/";

        public delegate void CopyOperationCompleteEventHandler(object sender, CopyOperationInfoEventArgs e);
        public event CopyOperationCompleteEventHandler Changed;

//======================================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="e"></param>
//======================================================================================================================
        private void OnChanged(CopyOperationInfoEventArgs e)
        {
            if (Changed != null)
                Changed(this, e);
        }

        #region Ctors
//======================================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="settings"></param>
//======================================================================================================================
        public StorageObjectRepository(RepositoryContext context) : base(context)
        {}
        #endregion
        #region Folder Operations
//======================================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="path"></param>
//======================================================================================================================
        public void MakeFolder(string path)
        {
            StoragePath targetPath      = new StoragePath(path);
            StorageObject storageObject = new StorageObject();

            if (targetPath.ResourcePath.EndsWith("/") == false)
            {
                storageObject.Key = targetPath.ResourcePath + @"/";
            }
            else
            {
                storageObject.Key = targetPath.ResourcePath;
            }

            storageObject.Load("");
            storageObject.StorageObjectType = StorageObjectType.Folder;
            HttpContent content = storageObject.ToHttpContent();
            content.Headers.Remove("Content-Type");
            content.Headers.Add("Content-Type", "application/directory");
            this.InvokeEndpoint(HttpMethod.Put, targetPath.AbsoluteURI, null, content, null);
        }
//======================================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="sourcePath"></param>
//======================================================================================================================
        public void DeleteFolder(string path)
        {
            StoragePath targetPath = new StoragePath(path);

            // Use recursion to grab a flat list of folder and file storageObjects from the supplied location.

            List<StorageObject> storageObjects = this.GetStorageObjects(targetPath, true);

            // Destroy everything we find.  

            if (storageObjects != null)
            {
                foreach (StorageObject storageObject in storageObjects)
                {
                    this.DeleteStorageObject(new StoragePath(this.Context.ServiceDescription.Url + cDelimiter + targetPath.Volume + cDelimiter + storageObject.Key));
                }
            }          
        }
////======================================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="sourcePath"></param>
///// <param name="targetPath"></param>
///// <param name="preserveMetaDataOnCopy"></param>
///// <param name="preserveACLDataOnCopy"></param>
///// <param name="useLocalFileMetaData"></param>
////======================================================================================================================
        private void CopyLocalToServer_Folder(StoragePath sourcePath, StoragePath targetPath, bool preserveMetaDataOnCopy, bool recurse, Action afterCopyAction)
        {
            List<string> fileNames = System.IO.Directory.GetFiles(sourcePath.Path).ToList();

            foreach (string filename in fileNames)
            {
                StorageObject storageObject = new StorageObject();
                storageObject.LoadFile(sourcePath.Path + filename);
                storageObject.Key = targetPath.Path;

                this.SaveStorageObject(storageObject);
            }
        }
//======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="sourcePath"></param>
/// <param name="targetPath"></param>
//======================================================================================================
        private void CopyFolderLocalToServer(StoragePath sourcePath, StoragePath targetPath, bool recursive)
        {
            Contract.Requires(sourcePath.PathType == PathType.Local);
            Contract.Requires(targetPath.PathType == PathType.Remote);
            Contract.Requires(targetPath.IsFolderPathOnly == true);
            Contract.Requires(sourcePath.IsFolderPathOnly == true);

            int copyCount     = 0;
            int folderCount   = 0;
            string localDrive = null;
            
            if (recursive)
            {
                DirectoryInfo info   = new DirectoryInfo(sourcePath.ResourcePath);

                // Create the folder for the one passed in (then we do its decendants)

                this.MakeFolder(targetPath.AbsoluteURI + sourcePath.LeafFolder + "/");
                folderCount++;

                DirectoryInfo[] dirInfos = info.GetDirectories("*.*", SearchOption.AllDirectories);

                for (int i = 0; i < dirInfos.Length; i++)
                {
                    // Strip out the local volume from the source Path..

                    string temp            = dirInfos[i].FullName.Replace(sourcePath.ResourcePath.Replace("/", @"\"), string.Empty);
                    string cleanFolderName = targetPath.AbsoluteURI + sourcePath.LeafFolder + "/" + temp + "/";
                    this.MakeFolder(cleanFolderName);
                    folderCount++;
                }

                FileInfo[] fileInfos = info.GetFiles("*.*", SearchOption.AllDirectories);

                for (int i = 0; i < fileInfos.Length; i++)
                {
                    if (fileInfos[i].Name != ".lock")
                    {
                        int pos                      = fileInfos[i].FullName.IndexOf(Path.VolumeSeparatorChar);
                        localDrive                   = fileInfos[i].FullName.Substring(0, pos + 2);
                        string tempfile              = fileInfos[i].FullName.Replace(sourcePath.ResourcePath.Replace("/", @"\"), string.Empty);
                        string cleanFileName         = targetPath.AbsoluteURI + sourcePath.LeafFolder + "/" + tempfile.Replace(@"\", "/");
                        cleanFileName                = cleanFileName.Replace(localDrive, string.Empty);
                        StoragePath remoteTargetPath = new StoragePath(cleanFileName);

                        this.CopyLocalToServer(new StoragePath(fileInfos[i].FullName), remoteTargetPath, false);
                        copyCount++;
                    }
                }
            }
            else
            {
                // We're just going to create the single directory specified (non-recuirsive)

                this.MakeFolder(targetPath.AbsoluteURI + sourcePath.LeafFolder);

                DirectoryInfo info   = new DirectoryInfo(sourcePath.ResourcePath);
                FileInfo[] fileInfos = info.GetFiles("*.*", SearchOption.TopDirectoryOnly);

                for (int i = 0; i < fileInfos.Length; i++)
                {
                    int pos                 = fileInfos[i].FullName.IndexOf(Path.VolumeSeparatorChar);
                    localDrive              = fileInfos[i].FullName.Substring(0, pos + 2);
                    string remoteTargetPath = targetPath.AbsoluteURI + sourcePath.LeafFolder + cDelimiter + fileInfos[i].Name.Replace(localDrive, string.Empty);

                    this.CopyLocalToServer(new StoragePath(fileInfos[i].FullName), new StoragePath(remoteTargetPath), false);
                    copyCount++;
                }
            }
        }
        #endregion
        #region Object Copy Operations
//==================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
//==================================================================================================
        private string ExtractPattern(string path)
        {
            path = path.TrimEnd('/');
            string[] elements = path.Split('/');
            return elements[elements.Length - 1];
        }
//======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
/// <param name="recursive"></param>
//======================================================================================================
        public void Copy(StorageObject source, string target, bool recursive)
        {
            // The User is copying a single file to the Server..

            StorageObject sObject           = new StorageObject();
            CopyOperationInfoEventArgs args = null;

            try
            {               
                OnChanged(new CopyOperationInfoEventArgs(sObject.FileName, sObject.Size));
                this.InvokeEndpoint(HttpMethod.Put, target, null, source.ToHttpContent(), null);
            }
            catch (UnauthorizedAccessException e)
            {
                args = new CopyOperationInfoEventArgs(sObject.FileName, 0);
                args.ExceptionMessage = e.Message;
                OnChanged(args);
            }
            catch (HttpRequestException ex)
            {
                args = new CopyOperationInfoEventArgs(sObject.FileName, 0);
                args.ExceptionMessage = ex.Message;
                OnChanged(args);
            }
        }
//======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
/// <param name="recursive"></param>
//======================================================================================================
        public void Copy(string source, string target, bool recursive = false)
        {
            StoragePath sourcePath = new StoragePath(source);
            StoragePath targetPath = new StoragePath(target);

            // Make sure that the User is not trying to perform a local copy only. this is an error as we don't duplicate cmd.exe functionality in this provider..

            if (sourcePath.PathType == PathType.Local && targetPath.PathType == PathType.Local)
            {
                throw new InvalidOperationException("Local file copy operations are not supported.");
            }
            else if (sourcePath.PathType == PathType.Local && targetPath.PathType == PathType.Remote)
            {
                this.CopyLocalToServer(sourcePath, targetPath, recursive);
            }
            else if (sourcePath.PathType == PathType.Remote && targetPath.PathType == PathType.Local)
            {
                this.CopyServerToLocal(sourcePath, targetPath, recursive);
            }
            else if (sourcePath.PathType == PathType.Remote && targetPath.PathType == PathType.Remote)
            {
                this.CopyIntraServer(sourcePath, targetPath, recursive);
            }
        }
//=========================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="sourcePath"></param>
/// <returns></returns>
//=========================================================================================
        private StorageObject CreateFolderStorageObject(string path)
        {
            StorageObject storageObject     = new StorageObject();
            storageObject.Key               = path;
            storageObject.StorageObjectType = StorageObjectType.Folder;

            return storageObject;
        }
//=========================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="sourcePath"></param>
/// <returns></returns>
//=========================================================================================
        private StorageObject CreateFolderStorageObject(StoragePath sourcePath)
        {
            StorageObject storageObject     = new StorageObject();
            storageObject.Key               = sourcePath.Path;
            storageObject.StorageObjectType = StorageObjectType.Folder;

            return storageObject;
        }
//======================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="sourcePath"></param>
///// <param name="targetPath"></param>
//======================================================================================================
        private void CopyIntraServer(StoragePath sourcePath, StoragePath targetPath, bool recursive)
        {
            Contract.Requires(targetPath.IsFolderPathOnly == true, "You must supply a valid folder for the target location argument.");

            // Make sure that the User isn't trying to copy a folder onto a file or something weird like that..
            // If not, and they want to do an IntraServer copy from one folder to another, handle it now..
            // Here's the situation, the IntraServer Copy is file based so deal with it..

            if (sourcePath.IsFolderPathOnly && targetPath.IsFolderPathOnly)
            {
                this.CopyFolderIntraServer(sourcePath, targetPath, recursive);
            }
            else
            {
                if (sourcePath.ContainsWildcard)
                {
                    StoragePath sPath         = new StoragePath(sourcePath.ResourcePath);
                    List<StorageObject> files = this.GetStorageObjects(sourcePath.ResourcePath, false);

                    foreach (StorageObject file in files)
                    {
                        sPath.FileName = this.ExtractPattern(file.FileName);
                        NameValueCollection headers = new NameValueCollection();
                        headers.Add(cCopySource, "/" + sPath.ResourcePath.Replace(@"\", "/"));
                        string tpath = targetPath.AbsoluteURI.Replace(this.ExtractPattern(targetPath.AbsoluteURI), String.Empty);
                        this.InvokeEndpoint(HttpMethod.Put, tpath + sPath.FileName , null, headers);
                        OnChanged(new CopyOperationInfoEventArgs(file.DisplayName, file.Size));
                    }
                }
                else
                {
                    NameValueCollection headers = new NameValueCollection();
                    headers.Add(cCopySource, "/" + sourcePath.ResourcePath);
                    this.InvokeEndpoint(HttpMethod.Put, targetPath.AbsoluteURI, null, headers);
                    List<KeyValuePair<string, string>> elements = this.GetMetaData(sourcePath.AbsoluteURI);
                    long lValue = long.Parse(elements.Where(name => name.Key == "Content-Length").Single().Value);
                    OnChanged(new CopyOperationInfoEventArgs(sourcePath.FileName, lValue));
                }
            }
        }
//======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
//======================================================================================================
        private void CopyFolderIntraServer(StoragePath sourcePath, StoragePath targetPath, bool recursive)
        {
            Contract.Requires(sourcePath != null && sourcePath.IsFolderPathOnly);
            Contract.Requires(targetPath != null && targetPath.IsFolderPathOnly);

            int copyCount   = 0;
            int folderCount = 0;
            string tempPath = null;
     
            List<StorageObject> storageObjects = this.GetStorageObjects(sourcePath, recursive);

            if (!recursive)
                storageObjects.RemoveAll(f => f.ObjectType == "Folder");

            // The recursive GetStorageObjects excludes the target folder so we manually add this in now..      

            if (storageObjects != null)
            {
                IEnumerable folders = storageObjects.ToList().Where(so => so.ObjectType == "Folder");
                if (folders != null)
                {
                    // Create our local directories first.

                    foreach (StorageObject storageObject in folders)
                    {
                        //tempPath = this.BuildFolderPath(sourcePath.LeafFolder, storageObject.Key);
                        if (storageObject.Key != string.Empty && storageObject.Key != null)
                        {
                            tempPath = targetPath.AbsoluteURI + storageObject.Key + "/";  
                            this.MakeFolder(tempPath);
                            folderCount++;
                        }
                    }
                }
            }

            // Now copy the files over to their respective locations..

            IEnumerable files          = storageObjects.ToList().Where(so => so.ObjectType == "File" && so.FileName != cFolderMarker);
            StoragePath CopyTargetPath = null;

            if (files != null)
            {
                foreach (StorageObject file in files)
                {
                    if (!file.Key.Contains(cFolderMarker))
                    {
                        StoragePath CopySourcePath = new StoragePath(sourcePath.ContainerPath + file.Key);

                        if (sourcePath.LeafFolder != "/" && sourcePath.LeafFolder != string.Empty)
                        {
                            string cutPathEnd = this.BuildFolderPath(sourcePath.LeafFolder, file.Key);
                            cutPathEnd        = cutPathEnd.Substring(0, cutPathEnd.Length - 1);
                            CopyTargetPath    = new StoragePath(targetPath.AbsoluteURI + cutPathEnd);
                        }
                        else
                        {
                            CopyTargetPath = new StoragePath(targetPath.AbsoluteURI + file.Key);
                        }

                        OnChanged(new CopyOperationInfoEventArgs(file.DisplayName, file.Size));
                        copyCount++;
                        this.CopyIntraServer(CopySourcePath, CopyTargetPath, recursive);
                    }
                }
            }
         }           
//=========================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="leaf"></param>
/// <param name="path"></param>
/// <returns></returns>
//=========================================================================================
        private string BuildFolderPath(string leaf, string path)
        {
            List<string> elements = path.Split('/').ToList<string>();
            int index = elements.IndexOf(leaf.TrimEnd('/'));
            string result = null;

            for (int i = index; i < elements.Count; i++)
            {
                result = result + elements[i] + "/";

            }
            return result.Replace("folder.txt/", string.Empty);
        }
////======================================================================================================================
///// <summary>
///// 
///// </summary>
////======================================================================================================================
        private void CopyLocalToServer(string targetPath, StorageObject storageObject)
        {
            storageObject.Key = targetPath;
            this.SaveStorageObject(storageObject);
        }
////======================================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="absolutePath"></param>
/// <param name="displayName"></param>
/// <returns></returns>
////======================================================================================================================
        private StorageObject GetStorageObjectContent(string absolutePath, string displayName)
        {
            string objSourcePath = absolutePath.Replace(this.ExtractPattern(absolutePath), string.Empty) + displayName;
            return this.GetStorageObject(objSourcePath);
        }
 //======================================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="sourcePath"></param>
///// <param name="targetPath"></param>
///// <param name="recurse"></param>
//======================================================================================================================
        private void CopyServerToLocal(StoragePath sourcePath, StoragePath targetPath, bool recursive)
        {
            if (sourcePath.IsFolderPathOnly && targetPath.IsFolderPathOnly)
            {
                this.CopyFolderServerToLocal(sourcePath, targetPath, recursive);
            }
            else
            {
                //Contract.Requires(sourcePath.IsFolderPathOnly == false);
                //Contract.Requires(targetPath.IsFolderPathOnly == true);

                if (this.PathContainsWildcards(sourcePath.ResourcePath))
                {
                    List<StorageObject> files = this.GetStorageObjects(sourcePath.AbsoluteURI, false);
                    foreach (StorageObject file in files)
                    {
                        string tPath          = targetPath.ResourcePath.Replace("/", @"\");
                        tPath                 = tPath.Replace(this.ExtractPattern(targetPath.ResourcePath), String.Empty) + this.ExtractPattern(file.FileName);
                        tPath                 = tPath.Replace("/", @"\");
                        FileStream stream     = new FileStream(tPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
                        StorageObject outFile = this.GetStorageObjectContent(sourcePath.AbsoluteURI, file.DisplayName);
                        outFile.Save(stream);
                        OnChanged(new CopyOperationInfoEventArgs(file.DisplayName, file.Size));
                    }
                }
                else
                {
                    // Verify the sourcePath is a valid argument..

                    StorageObject storageObject = this.GetStorageObject(sourcePath);
                    FileStream stream           = new FileStream(targetPath.ResourcePath.Replace("/", @"\"), FileMode.Create, FileAccess.ReadWrite, FileShare.None);

                    storageObject.Save(stream);
                    OnChanged(new CopyOperationInfoEventArgs(storageObject.DisplayName, storageObject.Size));
                }
            }
        }
//=========================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
//=========================================================================================
        private void CopyFolderServerToLocal(StoragePath sourcePath, StoragePath targetPath, bool recurse)
        {
            Contract.Requires(sourcePath != null);

            int copyCount = 0;
            int folderCount = 0;
         
            List<StorageObject> storageObjects = this.GetStorageObjects(sourcePath, recurse);
            
            if (!recurse)
            {
                storageObjects.RemoveAll(f => f.ObjectType == "Folder");

                // The recursive GetStorageObjects excludes the target folder so we manually add this in now..

                Directory.CreateDirectory((targetPath.ResourcePath + sourcePath.LeafFolder).Replace("/", @"\"));
                folderCount++;

                // Now copy the files over to their respective locations..

                IEnumerable files = storageObjects.ToList().Where(so => so.ObjectType == "File" && so.FileName != cFolderMarker);

                if (files != null)
                {
                    foreach (StorageObject file in files)
                    {
                        if (!file.Key.EndsWith("/"))
                        {
                            StorageObject singleObject = this.GetStorageObject(sourcePath.AbsoluteURI + Path.GetFileName(file.FileName));
                            FileStream fileStream      = new FileStream(targetPath.ResourcePath + file.Key, FileMode.Create, FileAccess.ReadWrite, FileShare.None);

                            OnChanged(new CopyOperationInfoEventArgs(file.DisplayName, file.Size));
                            copyCount++;
                            singleObject.Save(fileStream);
                        }
                    }
                }
                return;
            }

            if (storageObjects != null)
            {                
                IEnumerable folders = storageObjects.ToList().Where(so => so.ObjectType == "Folder");
                if (folders != null)
                {
                    // Create our local directories first...

                    foreach (StorageObject storageObject in folders)
                    {
                        Directory.CreateDirectory((targetPath.ResourcePath + storageObject.Key).Replace("/", @"\"));
                        folderCount++;
                    }
                }

                // Now copy the files over to their respective locations..

                IEnumerable files = storageObjects.ToList().Where(so => so.ObjectType == "File" && so.FileName != cFolderMarker);

                if (files != null)
                {
                    foreach (StorageObject file in files)
                    {
                        if (!file.Key.EndsWith("/"))
                        {
                            OnChanged(new CopyOperationInfoEventArgs(file.DisplayName, file.Size));
                            copyCount++;

                            int position               = sourcePath.Path.IndexOf(sourcePath.LeafFolder);
                            string targetFolder        = targetPath.ResourcePath + file.Key.Remove(0, position);
                            StorageObject singleObject = this.GetStorageObject(sourcePath.ContainerPath + file.Key);
                            FileStream fileStream      = new FileStream(targetFolder, FileMode.Create, FileAccess.ReadWrite, FileShare.None);

                            singleObject.Save(fileStream);
                        }
                    }
                }
            }
        }
////======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="sourcePath"></param>
/// <param name="targetPath"></param>
////======================================================================================================
        private void CopyFile(StoragePath sourcePath, StoragePath targetPath)
        {
            StorageObject sObject           = new StorageObject();
            CopyOperationInfoEventArgs args = null;

            try
            {
                if (!IsLargeFile(sourcePath))
                {
                    sObject.LoadFile(sourcePath);
                    targetPath.FileName = sourcePath.FileName;
                    sObject.Key = targetPath.ResourcePath;
                    OnChanged(new CopyOperationInfoEventArgs(sObject.FileName, sObject.Size));
                    this.InvokeEndpoint(HttpMethod.Put, targetPath.AbsoluteURI, null, sObject.ToHttpContent(), null);
                }
                else
                {
                    FileInfo info = new FileInfo(sourcePath.AbsoluteURI);
                    int segmentSize = Convert.ToInt32(info.Length) / 4;
                    this.CopyLargeFile(sourcePath.AbsoluteURI, segmentSize, targetPath);
                    OnChanged(new CopyOperationInfoEventArgs(sObject.FileName, info.Length));
                }               
            }
            catch (UnauthorizedAccessException e)
            {
                args = new CopyOperationInfoEventArgs(sObject.FileName, 0);
                args.ExceptionMessage = e.Message;
                OnChanged(args);
            }
            catch (HttpRequestException ex)
            {
                args = new CopyOperationInfoEventArgs(sObject.FileName, 0);
                args.ExceptionMessage = ex.Message;
                OnChanged(args);
            }
        }
////======================================================================================================
/// <summary>
/// 
/// </summary>
////======================================================================================================
        private void SendManifest(string prefix, string tPath)
        {
            string filename             = Path.GetFileName(tPath);
            StoragePath targetPath      = new StoragePath(tPath.Replace(filename, "/temp/" + filename));
            StorageObject storageObject = new StorageObject();

            storageObject.Load("");
            storageObject.StorageObjectType = StorageObjectType.Folder;
            HttpContent content = storageObject.ToHttpContent();
            content.Headers.Remove("Content-Type");
            content.Headers.Add("X-Object-Manifest", prefix.Replace(@"\", "/").Replace(filename, "temp/" + filename));
            Console.WriteLine("Sending segment manifest.");
            this.InvokeEndpoint(HttpMethod.Put, targetPath.AbsoluteURI, null, content, null);
        }
////======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="path"></param>
////======================================================================================================
        private string CreateTempFolder(string path)
        {
            string filename = Path.GetFileName(path);
            string newPath  = path.Replace(filename, "temp/");
            this.MakeFolder(this.Context.ServiceDescription.Url + "/" + newPath);
            return newPath;
        }
////======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="filePath"></param>
/// <param name="chunkSize"></param>
/// <returns></returns>
////======================================================================================================
        private long CalculateSegments(string filePath, long chunkSize)
        {
            FileInfo info = new FileInfo(filePath);
            return info.Length / chunkSize;            
        }
////======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="inputFile"></param>
/// <param name="chunkSize"></param>
/// <param name="path"></param>
////======================================================================================================
        private void CopyLargeFile(string inputFile, int chunkSize, StoragePath targetPath)
        {
            string path       = targetPath.ResourcePath.Replace("/", @"\");
            byte[] buffer     = new byte[chunkSize];
            string tempFolder = this.CreateTempFolder(path);
            string key        = tempFolder + Path.GetFileName(path);
            long numSegments  = this.CalculateSegments(inputFile, chunkSize);
            
            using (Stream input = File.OpenRead(inputFile))
            {
                int index = 0;
                while (input.Position < input.Length)
                {
                    using (Stream output = new MemoryStream())
                    {
                        int chunkBytesRead = 0;
                        while (chunkBytesRead < chunkSize)
                        {
                            int bytesRead = input.Read(buffer,
                                                       chunkBytesRead,
                                                       chunkSize - chunkBytesRead);

                            if (bytesRead == 0)
                            {
                                break;
                            }
                            chunkBytesRead += bytesRead;
                        }
                                           
                        StorageObject segment = new StorageObject();
                        segment.Key           = (key + Convert.ToString(input.Position)).Replace("/", @"\");
                        segment.Load(buffer);
                        Console.WriteLine("Writing segment " + Convert.ToString(index) + " , " + "out of " + numSegments);
                        this.SaveStorageObject(segment);
                    }
                    index++;
                }
            }

            this.SendManifest(targetPath.ResourcePath.Replace("/", @"\"), targetPath.AbsoluteURI);
            this.CompleteFileAssembly(targetPath.AbsoluteURI);
        }
////======================================================================================================
/// <summary>
/// 
/// </summary>
////======================================================================================================
        private void CompleteFileAssembly(string sourcePath)
        {
            Console.WriteLine("Completing file assembly process.");
            string filename   = Path.GetFileName(sourcePath);
            StoragePath sPath = new StoragePath(sourcePath.Replace(filename, "/temp/" + filename));
            try
            {
                this.CopyIntraServer(sPath, new StoragePath(sourcePath.Replace("/temp/", string.Empty)), false);
            }
            catch (Exception ex) { }
            this.DeleteFolder(sourcePath.Replace(filename, "/temp/"));
        }
////======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
////======================================================================================================
        private bool IsLargeFile(StoragePath filePath)
        {
            FileInfo info = new FileInfo(filePath.AbsoluteURI);
            if (info.Length > 500000)
                return true;
            else
                return false;
        }
////======================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="targetPath"></param>
///// <param name="storageObject"></param>
////======================================================================================================
        private void CopyLocalToServer(StoragePath sourcePath, StoragePath targetPath, bool recursive = false)
        {
            Contract.Requires(targetPath.IsFolderPathOnly && targetPath.PathType == PathType.Remote);
            CopyOperationInfoEventArgs args = null;

            // See if we're copying a local folder to the Server..

            if (sourcePath.IsFolderPathOnly && targetPath.IsFolderPathOnly)
            {
                this.CopyFolderLocalToServer(sourcePath, targetPath, recursive);
                return;
            }
            else
            {
                // The User is copying a single file or a set of local files matching a pattern to the Server..

                string basePath = sourcePath.BasePath.Replace("/", @"\");
   
                // See if the User is supplying us wildcards in the SourcePath.

                if (this.PathContainsWildcards(basePath))
                {
                    string removedWilcardPath   = this.ExtractPattern(sourcePath.BasePath);
                    basePath                    = basePath.Replace(removedWilcardPath, String.Empty);
                    DirectoryInfo info          = new DirectoryInfo(basePath);
                    IEnumerable<FileInfo> files = info.EnumerateFiles(removedWilcardPath, SearchOption.TopDirectoryOnly);

                    foreach (FileInfo file in files)
                    {
                        StoragePath sPath = new StoragePath(basePath + @"\" + file.Name);
                        this.CopyFile(sPath, targetPath);
                    }        
                }
                else
                {
                    this.CopyFile(sourcePath, targetPath);                                    
                }
            }
        }
        #endregion
        #region Objects - Delete Operations
////======================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="targetPath"></param>
///// <param name="permanentDelete"></param>
////======================================================================================================
        private void DeleteStorageObject(StoragePath targetPath)
        {
            this.InvokeEndpoint(HttpMethod.Delete, targetPath.AbsoluteURI, null, null);
        }
////======================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="targetPath"></param>
///// <param name="permanentDelete"></param>
////======================================================================================================
        public void DeleteStorageObject(string path)
        {
            if (this.PathContainsWildcards(path))
            {
                List<StorageObject> storageObjects = this.GetStorageObjects(path, false);
           
                foreach (StorageObject file in storageObjects) 
                {
                    StoragePath targetPath = new StoragePath(path);
                    targetPath.FileName    = this.ExtractPattern(file.FileName);
                    this.InvokeEndpoint(HttpMethod.Delete, targetPath.AbsoluteURI, null, null);
                }
            }
            else
            {
                StoragePath targetPath = new StoragePath(path);
                this.InvokeEndpoint(HttpMethod.Delete, targetPath.AbsoluteURI, null, null);
            }
        }
        #endregion
        #region Objects - Retrieval and Persistence Operations
////======================================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="path"></param>
///// <returns></returns>
////======================================================================================================================
        public void SetMetaData(string source, List<KeyValuePair<string, string>> kvp)
        {
            Contract.Requires(source != null, "You must supply a valid StoragePath to complete this operation.");
            Contract.Requires(Path.GetFileName(source) != null);

            StoragePath sourcePath = new StoragePath(source);
            HttpClient client      = this.CreateHttpClient();
            
            foreach (KeyValuePair<string, string> element in kvp)
            {
                client.DefaultRequestHeaders.Add("X-Object-Meta-" + element.Key, element.Value);
            }

            StorageObject so = new StorageObject();
            so.Load("");
            HttpResponseMessage msg = client.PostAsync(sourcePath.ToUri(), so.ToHttpContent()).Result;           
        }
////======================================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="path"></param>
///// <returns></returns>
////======================================================================================================================
        public List<KeyValuePair<string, string>> GetMetaData(string source)
        {
            Contract.Requires(source != null, "You must supply a valid, remote source path to complete this operation.");

            StoragePath sourcePath     = new StoragePath(source);
            HttpClient client          = this.CreateHttpClient();         
            HttpRequestMessage request = this.CreateRequestMessage(HttpMethod.Head, sourcePath.ToUri(), null);
            HttpResponseMessage msg    = client.SendAsync(request).Result;

            if (this.IsStatusNormal(msg.StatusCode))
            {
                msg.Content.LoadIntoBufferAsync();
                List<KeyValuePair<string, string>> extendedProperties = new List<KeyValuePair<string, string>>();

                foreach (KeyValuePair<string, IEnumerable<string>> header in msg.Headers)
                {
                    if (header.Key.StartsWith("X-Object-Meta"))
                    {
                        extendedProperties.Add(new KeyValuePair<string, string>(header.Key, header.Value.First()));
                    }                   
                }

                extendedProperties.Add(new KeyValuePair<string, string>("Content-Length", Convert.ToString(msg.Content.Headers.ContentLength)));
                return extendedProperties;
            }
            else
            {
                //throw ExceptionFactory.CreateStorageObjectRepositoryException(msg);
            }
            
            return null;
        }
////======================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="sourcePath"></param>
///// <returns></returns>
////======================================================================================================
        private Domain.StorageObject GetStorageObject(StoragePath sourcePath)
        {
            HttpClient client       = this.CreateHttpClient();           
            HttpResponseMessage msg = client.GetAsync(sourcePath.ToUri()).Result;

            if (this.IsStatusNormal(msg.StatusCode))
            {
                msg.Content.LoadIntoBufferAsync();
                StorageObject storageObject = new StorageObject();
                storageObject.Key           = sourcePath.ResourcePath;
                storageObject.Load(msg.Content.ReadAsStreamAsync().Result);
                return storageObject;
            }
            else
            {
                return null;
                // throw ExceptionFactory.CreateStorageObjectRepositoryException(msg);
            }
            
        }
////======================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="sourcePath"></param>
///// <returns></returns>
////======================================================================================================
        public Domain.StorageObject GetStorageObject(string sourcePath)
        {
            StoragePath path        = new StoragePath(sourcePath);
            HttpClient client       = this.CreateHttpClient();                      
            HttpResponseMessage msg = client.GetAsync(path.ToUri()).Result;

            if (this.IsStatusNormal(msg.StatusCode))
            {
                msg.Content.LoadIntoBufferAsync();
                StorageObject storageObject = new StorageObject();
                storageObject.Key           = path.ResourcePath;

                storageObject.Load(msg.Content.ReadAsStreamAsync().Result);

                return storageObject;
            }
            else
            {
                return null;
                // throw ExceptionFactory.CreateStorageObjectRepositoryException(msg);
            }
           
        }
////======================================================================================================================
///// <summary>
///// 
///// </summary>
///// <returns></returns>
////======================================================================================================================
        static StorageObjects GetNodeContents(StoragePath sourcePath, string storageManagementUrl, string authToken)
        {
            Contract.Requires(sourcePath.IsFolderPathOnly == true, "You must pass a path to a folder to complete this operation.");

            NameValueCollection queryString = new NameValueCollection();

            if (sourcePath.Path != "")
                queryString.Add("path", sourcePath.Path);
            else
                queryString.Add("path", "");

            StorageObjects storageObjects = BaseRepository.ListObjects<StorageObjects>(HttpMethod.Get, storageManagementUrl + cDelimiter + sourcePath.Volume, queryString, null, authToken);
            return storageObjects;
        }
////======================================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="sourcePath"></param>
///// <returns></returns>
////======================================================================================================================
        static bool GetStorageObjectsHierachy(StoragePath sourcePath, ref StorageObjects storageObjects, string storageManagementUrl, string authToken)
        {
            Contract.Requires(sourcePath.IsFolderPathOnly);
            StorageObjects nextNodeContents = GetNodeContents(sourcePath, storageManagementUrl, authToken);

            if (!nextNodeContents.List.Any())
            {
                return false;
            }
            else
            {
                storageObjects.List.AddRange(nextNodeContents.List);
                List<StorageObject> folders = nextNodeContents.GetFolders();

                if (folders != null)
                {
                    // This code is crap...find out why we did this...

                    foreach (StorageObject folder in folders)
                    {
                        if (GetStorageObjectsHierachy(new StoragePath(storageManagementUrl + "/" + sourcePath.Volume + "/" + folder.Key), ref storageObjects, storageManagementUrl, authToken) == false)
                        {
                            //return false;
                        }
                        else
                        {
                            //return true;
                        }
                    }
                }
            }
            return false;
        }
////======================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="sourcePath"></param>
///// <param name="recurse"></param>
///// <returns></returns>
////======================================================================================================
        private List<StorageObject> GetStorageObjects(StoragePath sourcePath, bool recurse)
        { 
            Contract.Requires(sourcePath.IsFolderPathOnly == true, "You must pass a path to a folder to complete this operation.");
            Contract.Requires(sourcePath.PathType == PathType.Remote, "You must pass a path to a remote folder to complete this operation.");

            NameValueCollection queryString = new NameValueCollection();
            if (sourcePath.Path != "" && sourcePath.Path != "/")
            {
                if (sourcePath.Path.StartsWith("/"))
                    queryString.Add("path", sourcePath.Path.TrimStart('/'));
                else
                    queryString.Add("path", sourcePath.Path);
            }
            else
                queryString.Add("path", "");

            if (recurse)
            {
                StorageObjects hierarchy = new StorageObjects();
                GetStorageObjectsHierachy(sourcePath, ref hierarchy, this.Context.ServiceDescription.Url, this.Context.AccessToken.Id);

                StorageObject root     = new StorageObject();
                root.ContentType       = "application/directory";
                root.StorageObjectType = StorageObjectType.Folder;
                root.Key               = sourcePath.Path + "/";

                hierarchy.List.Add(root);
                return hierarchy.List.ToList<StorageObject>();
            }
            else
            {
                StorageObjects storageObjects = this.InvokeEndpoint<StorageObjects>(HttpMethod.Get, this.Context.ServiceDescription.Url + cDelimiter + sourcePath.Volume, queryString, null);
                return storageObjects.List.ToList<StorageObject>(); 
            }
        }        
////======================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
////======================================================================================================
        private bool PathContainsWildcards(string path)
        {
            if (path.Contains("?") || path.Contains("*") || path.Contains("[") || path.Contains("]"))
                return true;
            else
                return false;
        }
////======================================================================================================
///// <summary>
///// 
///// </summary>
///// <param name="sourcePath"></param>
///// <param name="recurse"></param>
///// <returns></returns>
////======================================================================================================
        public List<StorageObject> GetStorageObjects(string path, bool recurse)
        {
            StoragePath sourcePath = new StoragePath(path);

            //Contract.Requires(sourcePath.IsFolderPathOnly == true, "You must pass a path to a folder to complete this operation.");
            //Contract.Requires(sourcePath.PathType == PathType.Remote, "You must pass a path to a remote folder to complete this operation.");

            NameValueCollection queryString = new NameValueCollection();
            if (sourcePath.Path != "" && sourcePath.Path != "/")
            {
                if (sourcePath.Path.StartsWith("/"))
                    queryString.Add("path", sourcePath.Path.TrimStart('/'));
                else
                {
                    if (this.PathContainsWildcards(sourcePath.Path))
                    {
                        if (sourcePath.IsRoot)
                           queryString.Add("path",  String.Empty);
                        else
                            queryString.Add("path", sourcePath.Path.Replace("/" + this.ExtractPattern(sourcePath.Path), String.Empty));
                    }
                    else
                    {
                        queryString.Add("path", sourcePath.Path);
                    }
                }
            }
            else
                queryString.Add("path", "");

            if (recurse)
            {
                StorageObjects hierarchy = new StorageObjects();
                GetStorageObjectsHierachy(sourcePath, ref hierarchy, this.Context.ServiceDescription.Url, this.Context.AccessToken.Id);
                return hierarchy.List.ToList<StorageObject>();
            }
            else
            {
                if (this.PathContainsWildcards(path))
                {
                    StorageObjects storageObjects = this.InvokeEndpoint<StorageObjects>(HttpMethod.Get, this.Context.ServiceDescription.Url + cDelimiter + sourcePath.Volume, queryString, null);
                    string userPattern            = this.ExtractPattern(path);
                    WildcardOptions options       = WildcardOptions.Compiled | WildcardOptions.IgnoreCase;
                    WildcardPattern pattern       = new WildcardPattern(userPattern, options);
                   
                    //return (from item in storageObjects.List.ToList<StorageObject>()
                    //        where pattern.IsMatch(item.FileName)
                    //        select item).ToList<StorageObject>();

                    return (from item in storageObjects.List.ToList<StorageObject>()
                            where pattern.IsMatch(this.GetCleanFilename(item.FileName))
                            select item).ToList<StorageObject>();
                }
                else
                {
                    StorageObjects storageObjects = this.InvokeEndpoint<StorageObjects>(HttpMethod.Get, this.Context.ServiceDescription.Url + cDelimiter + sourcePath.Volume, queryString, null);
                   // StorageObjects storageObjects = this.InvokeEndpoint<StorageObjects>(HttpMethod.Get, sourcePath.ContainerPath, queryString, null);
                    return storageObjects.List.ToList<StorageObject>();
                }
            }
            return null;
        }
//======================================================================================================================
/// <summary>
/// 
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
//======================================================================================================================
        private string GetCleanFilename(string filename)
        {
            return Path.GetFileName(filename.Replace("/", @"\"));
        }
//////======================================================================================================================
/////// <summary>
/////// 
/////// </summary>
/////// <param name="storageObject"></param>
//////======================================================================================================================
        public void SaveStorageObject(StorageObject storageObject)
        {
            try
            {
                string resourcePath = this.Context.ServiceDescription.Url + "/" + storageObject.Key.Replace(@"\", @"/");
                this.InvokeEndpoint(HttpMethod.Put, resourcePath, null, storageObject.ToHttpContent(), null);
            }
            catch (Exception ex)
            {
                int h = 8;
            }
        }
        #endregion
    }
}
